
#include <cstdio>
#include <BTextEdit>
#include <BStyle>
#include <BPainter>
#include <BXmlDocument>
#include <BApplication>

#include "YamlFormat.h"

YamlFormat::YamlFormat(const BString& name) : BTextFormat(name)
{
	_style = 0;
	_textEdit = 0;
	_treeWidget = 0;

	_dockLayout = new BDockLayout();
	_dockLayout->setPart(Part_Center);
	_dockLayout->setSplitable(true);
	_dockLayout->setSpacing(5);

	//createTreeWidget();

	connect(this, Signal_AssetChanged, &YamlFormat::slotAssetChanged);

}
YamlFormat::~YamlFormat()
{
	if (_textEdit)
	{
		disconnect(_textEdit, Signal_Framed);
	}
}

int YamlFormat::topItemCount() const
{
	return _topItems.size();
}
YamlItem* YamlFormat::topItem(int index)
{
	return _topItems.at(index);
}

void YamlFormat::styled(const BStyle* style)
{
	_style = style;
	_colorMap[YType_Item].set(0, 0, 128);
	_colorMap[YType_Array].set(0, 56, 100);
	_colorMap[YType_Map].set(0, 100, 56);
	_colorMap[YType_Name].set(128, 0, 128);
	_colorMap[YType_Value].set(128, 0, 0);
	_colorMap[YType_Annotation].set(0, 128, 0);

	if (style)
	{
		if (style->hasColor("type-item"))
			_colorMap[YType_Item] = style->color("type-item");
		if (style->hasColor("type-name"))
			_colorMap[YType_Name] = style->color("type-name");
		if (style->hasColor("type-value"))
			_colorMap[YType_Value] = style->color("type-value");
		if (style->hasColor("type-annotation"))
			_colorMap[YType_Annotation] = style->color("type-annotation");
	}
	
	this->emit(Signal_Styled, style);
}
void YamlFormat::changed(const BString& xmltext)
{
	readDocument(xmltext);
	this->emit(Signal_Changed);
}

const BFont* YamlFormat::font(int pos) const
{
	return 0;
}
const BColor* YamlFormat::back(int pos) const
{
	return 0;
}
const BColor* YamlFormat::color(int pos) const
{
	auto it = _itemMap <= pos;
	if (YamlItem* item = it.value())
	{
		const BColor& color = _colorMap(item->type);
		if (pos < item->range.max() && color.a() > 0)
			return &color;
	}
	return 0;
}

void YamlFormat::createTreeWidget()
{
	_treeWidget = new BTreeWidget();
	_treeWidget->setWidthPolicy(Policy_Preferred | Policy_Expanding);
	_dockLayout->addWidget(_treeWidget, Align_RightTop);
	_dockLayout->setStretch(_treeWidget, BSizeStretch(0.1f, 0.5f));
}
void YamlFormat::freshTreeWidget()
{
	if (_treeWidget)
	{
		_treeWidget->clearItems();
		for (int i = 0; i < _topItems.size(); i++)
		{
			BTreeItem* item = createTreeItem(_topItems[i]);
			_treeWidget->addItem(item);
		}
	}
}
BTreeItem* YamlFormat::createTreeItem(YamlItem* item)
{
	const BStyle* realStyle = _treeWidget->realStyle();
	BTreeItem* treeItem = new BTreeItem(item->text);
	treeItem->setAsset(item);
	for (int i = 0; i < item->children.size(); i++)
	{
		BTreeItem* childItem = createTreeItem(item->children[i]);
		treeItem->addChild(childItem);
	}
	return treeItem;
}

YamlItem* YamlFormat::insertYamlItem(int pos, int count, YamlType type, YamlItem* parent)
{
	YamlItem* item = new YamlItem();
	item->type = type;
	item->range.min() = pos;
	item->range.max() = pos + count;
	if (parent)
	{
		item->parent = parent;
		parent->children.append(item);
	}
	else
	{
		_topItems.append(item);
	}
	_itemMap.insert(pos, item);
	return item;
}

bool YamlFormat::readDocument(const BString& yamltext)
{
	_topItems.reset();
	_itemMap.reset();
	if (yamltext.empty())
		return false;

	BString name;
	int begin = 0;
	char code = 0;
	int pos = 0;

	BString line;
	while (pos <= yamltext.size())
	{
		char code = yamltext[pos];
		if (code == '[')
		{
			YamlItem* item = insertYamlItem(begin, pos - begin, YType_Array);
			this->readArray(item, yamltext, pos);
			continue;
		}
		if (code == '{')
		{
			YamlItem* item = insertYamlItem(begin, pos - begin, YType_Map);
			this->readMap(item, yamltext, pos);
			continue;
		}
		if (code == '\n' || code == 0)
		{
			if (code == 0)
				int a = 0;
			yamltext.substr(line, begin, pos);
			int indent = this->readIndent(line);
			YamlItem* parent = this->upperItem(indent);

			int comm_pos = line.find('#');
			if (comm_pos < line.size())
			{
				YamlItem* commItem = insertYamlItem(begin + comm_pos, line.count() - comm_pos, YType_Annotation, parent);
				line.truncate(0, comm_pos);
			}
			if (line[indent] == '-')
			{
				YamlItem* item = insertYamlItem(begin + indent, comm_pos - begin, YType_Array);
				item->indent = indent;
			}
			else
			{
				int split_pos = line.find(':', 0, comm_pos);
				if (split_pos < comm_pos)
				{
					if (split_pos - indent > 1)
					{
						YamlItem* nameItem = insertYamlItem(begin + indent, split_pos - indent, YType_Name, parent);
						nameItem->indent = indent;
					}
					if (comm_pos - split_pos > 1)
					{
						BRange range = trimmedString(line, split_pos + 1, comm_pos);
						if (range.size())
						{
							YamlItem* valueItem = insertYamlItem(begin + range.min(), range.size(), YType_Value, parent);
						}
					}
				}
			}
			begin = pos + 1;
		}
		pos++;
	}
	for (int i = 0; i < _topItems.size(); i++)
	{
		this->arrangeIndent(_topItems[i]);
	}

	return _topItems.size();
}

int YamlFormat::readIndent(const BString& text)
{
	int pos = 0;
	while (pos < text.size())
	{
		char c = text[pos];
		if (c == ' ' || c == '\t')
			pos++;
		else
			break;
	}
	return pos;
}
YamlItem* YamlFormat::upperItem(int indent)
{
	YamlItem* item = _topItems.last();
	while (item && item->indent >= indent)
	{
		item = item->parent;
	}
	return item;
}
void YamlFormat::arrangeIndent(YamlItem* item)
{
	for (int i = 0; i < item->children.size(); i++)
	{
		YamlItem* child = item->children(i);
		arrangeIndent(child);
	}
	if (item->parent)
	{
		item->indent -= item->parent->indent;
	}
}
BRange YamlFormat::trimmedString(const BString& text, int begin, int end)
{
	if (end > text.size())
		end = text.size();
	if (begin < end)
	{
		while (begin < end)
		{
			if (text[begin] < 0)
				break;
			if (!isspace(text[begin]))
				break;
			begin++;
		}
		while (begin < end)
		{
			if (text[end - 1] < 0)
				break;
			if (!isspace(text[end - 1]))
				break;
			end--;
		}
	}
	return BRange(begin, end);
}

BThing* YamlFormat::readArray(YamlItem* item, const BString& line, int pos)
{
	BArray<BThingHolder>* array = new BArray<BThingHolder>;
	BString text;
	int depth = 1;
	char code = 0;
	while (pos < line.size())
	{
		code = line[pos];
		if (code == ',')
		{
			text.trimmed();
			array->append(new BString(text));
			text.reset();
			continue;
		}
		if (code == '{')
		{
			BThing* map = readMap(item, line, pos);
			array->append(map);
			continue;
		}
		if (code == '[')
		{
			BThing* child = readArray(item, line, pos);
			array->append(child);
		}
		if (code == ']')
		{
			text.trimmed();
			if (text.size())
				array->append(new BString(text));
			depth--;
			if (depth == 0)
				break;
		}
		text << code;
	}
	return array;
}
BThing* YamlFormat::readMap(YamlItem* item, const BString& line, int pos)
{
	int begin = 0;
	BString text;
	int depth = 1;
	char code = 0;
	while (pos < line.size())
	{
		code = line[pos];
		if (code == ',')
		{
			text.trimmed();
			int split = text.find(':');
			if (split < text.size())
			{
				BString name;
				text.substr(name, 0, split);
				text.remove(0, split + 1);
				text.trimmed();

				YamlItem* nameItem = insertYamlItem(begin, pos - begin, YType_Name, item);
				nameItem->text = name;

				YamlItem* valueItem = insertYamlItem(begin, pos - begin, YType_Value, item);
				valueItem->text = text;
			}
			else
			{
				perror("map without ':'");
			}
			text.reset();
			continue;
		}
		if (code == '[')
		{
			BThingHolder child = readArray(item, line, pos);
			int split = text.find(':');
			if (split < text.size())
			{
				BString name;
				text.substr(name, 0, split);
				name.trimmed();

				YamlItem* nameItem = insertYamlItem(begin, pos - begin, YType_Name, item);
				nameItem->text = name;

				YamlItem* valueItem = insertYamlItem(begin, pos - begin, YType_Value, item);
				valueItem->text = text;
			}
			else
			{
				perror("map without ':'");
			}
			text.reset();
			continue;
		}
		if (code == '}')
		{
			depth--;
			if (depth == 0)
				break;
		}
		text << code;
	}
	return 0;
}

void YamlFormat::slotAssetChanged(BObject* object, const BValue& value)
{
	BTextEdit* textEdit = value;
	if (_textEdit != textEdit)
	{
		if (_textEdit)
		{
			disconnect(_textEdit, Signal_Framed);
		}
		_textEdit = textEdit;
		if (_textEdit)
		{
			//connect(_textEdit, Signal_Framed, &YamlFormat::slotTextEditFramed);
		}
	}
}
void YamlFormat::slotTextEditFramed(BTextEdit* textEdit, const BValue& value)
{
	if (_textEdit)
	{
		const BIntArray& lines = textEdit->query("visual-lines");
		if (lines.size())
		{
			BPainter painter(textEdit);
			painter.setColor(Color_Text);

			BRange firstRange = textEdit->lineRange(lines.first());
			BRange lastRange = textEdit->lineRange(lines.last());
			auto begin = _itemMap >= firstRange.min();
			auto end = _itemMap > lastRange.max();
			if (begin.valid() && begin < end)
			{
				for (auto it = begin; it < end; it++)
				{
					YamlItem* item = *it;
					if (it.key() == 6)
						int a = 0;
					BRect rect = textEdit->codeRect(it.key());
					for (int i = item->range.min(); i < item->range.max(); i++)
					{
						BRect codeRect = textEdit->codeRect(i);
						rect.expand(codeRect);
					}
					painter.drawRect(rect);
				}
			}
		}
	}
}

